package org.aldar.bquest.entity;

import org.aldar.bquest.constants.GameCycleState;
import org.aldar.bquest.constants.IConstants;
import org.aldar.bquest.constants.LandscapeType;
import org.aldar.bquest.constants.TileState;
import org.aldar.bquest.effects.MoveSmoothModifier;
import org.aldar.bquest.entity.behaviour.AfterMatchEntityModifierListener;
import org.aldar.bquest.entity.effect.ParticleUtil;
import org.aldar.bquest.entity.pool.TilesPool;
import org.aldar.bquest.logic.TilesGameCycle;
import org.aldar.bquest.logic.controller.board.transform.TileBoardTransform;
import org.aldar.bquest.logic.controller.entity.HeroAnimationController;
import org.aldar.bquest.logic.controller.entity.TileBoard;
import org.aldar.bquest.resources.ResourceManager;
import org.aldar.bquest.screens.MainBoardScreen;
import org.aldar.bquest.util.Logger;
import org.andengine.engine.handler.timer.ITimerCallback;
import org.andengine.engine.handler.timer.TimerHandler;
import org.andengine.entity.Entity;
import org.andengine.entity.IEntity;
import org.andengine.entity.modifier.*;
import org.andengine.entity.sprite.AnimatedSprite;
import org.andengine.entity.sprite.Sprite;
import org.andengine.opengl.texture.region.ITextureRegion;
import org.andengine.opengl.texture.region.ITiledTextureRegion;
import org.andengine.opengl.texture.region.TiledTextureRegion;
import org.andengine.opengl.vbo.VertexBufferObjectManager;
import org.andengine.util.modifier.IModifier;

import java.util.LinkedList;
import java.util.Random;

public class TileSprite implements ISprite, IConstants {

    private static final Logger LOG = new Logger(TileSprite.class);


    LandscapeType mStyle;
    TileState mState;
    Entity tileGroundContainer;
    Entity tileAboveContainer;
    Entity effectContainer;
    private Entity groundLayer;
    private Entity aboveLayer;

    private AnimatedSprite enemyImp;
    private AnimatedSprite fireOnTile;
    private Sprite lsSprite;

    private int currentCol;
    private int currentRow;
    private boolean matchActionPerformed;
    private boolean enemyImpInAction;
    private boolean isInDrag;
    private boolean isInMovement;

    protected Runnable temporaryMatchEffect = null;
    protected float temporaryMatchDissappearDelay = 0;
    private boolean useDefaultDissolveAction;

    public TileSprite(int col, int row, ITextureRegion mTileTextureRegion, final VertexBufferObjectManager pVertexBufferObjectManager) {
        this.tileGroundContainer = createTileEntity(col, row, mTileTextureRegion, pVertexBufferObjectManager);
        this.tileAboveContainer = new Entity((int) (TileBoardTransform.getSpriteAdjustedScreenCoords(TileBoardTransform.getScreenCoords(col * CELL_WIDTH, row * CELL_HEIGHT))[0]),
                (int) (TileBoardTransform.getSpriteAdjustedScreenCoords(TileBoardTransform.getScreenCoords(col * CELL_WIDTH, row * CELL_HEIGHT))[1]),
                100, 110) {
            public String toString() {
                return "entity:TileAboveContainer";
            }
        };
        this.tileAboveContainer.setAnchorCenter(0, 1);
        this.effectContainer = new Entity();
        this.mState = TileState.STATE_NORMAL;
    }

    public Entity createTileEntity(int col, int row, ITextureRegion mTileTextureRegion, VertexBufferObjectManager pVertexBufferObjectManager) {
        Entity entity = new Entity((int) (TileBoardTransform.getSpriteAdjustedScreenCoords(TileBoardTransform.getScreenCoords(col * CELL_WIDTH, row * CELL_HEIGHT))[0]),
                (int) (TileBoardTransform.getSpriteAdjustedScreenCoords(TileBoardTransform.getScreenCoords(col * CELL_WIDTH, row * CELL_HEIGHT))[1]),
                100, 110) {
            public String toString() {
                return "entity:TileGroundContainer:" + getStyle().toString();
            }
        };
        entity.setAnchorCenter(0, 1);
        if (mTileTextureRegion instanceof TiledTextureRegion) {
            AnimatedSprite anim = new AnimatedSprite(0, 0,
                    (ITiledTextureRegion) mTileTextureRegion,
                    pVertexBufferObjectManager
            );
            anim.setAnchorCenter(0, 0);
            anim.animate(50, getAnimationListener(((ITiledTextureRegion) mTileTextureRegion).getTileCount()));
            lsSprite = anim;
            entity.attachChild(lsSprite);
        } else {
            lsSprite = new CustomSprite(0, 0, mTileTextureRegion, pVertexBufferObjectManager);
            lsSprite.setPosition(0, 0);
            lsSprite.setAnchorCenter(0, 0);
            entity.attachChild(lsSprite);
        }
        return entity;
    }

    @Override
    public void setMapPosition(int col, int row) {
        float[] screenXY = TileBoardTransform.getSpriteAdjustedScreenCoords(
                TileBoardTransform.getScreenCoords(col * CELL_WIDTH, row * CELL_HEIGHT));
        this.tileAboveContainer.setPosition(screenXY[0], screenXY[1]);
        this.tileGroundContainer.setPosition(screenXY[0], screenXY[1]);
    }

    @Override
    public boolean producesMana() {
        return false;
    }

    public LandscapeType getStyle() {
        return mStyle;
    }

    public void setStyle(LandscapeType mStyle) {
        this.mStyle = mStyle;
    }

    public Entity getTile() {
        return this.tileGroundContainer;
    }

    public void setState(TileState state) {
        LOG.d("Setting tile to state = " + state);
        this.mState = state;
    }

    public TileState getState() {
        return this.mState;
    }

    public void setScreenPosition(float screeX, float screenY) {
        this.getTileGroundContainer().setPosition(screeX, screenY);
        this.getTileAboveContainer().setPosition(screeX, screenY);
    }

    public void attachToLayer(Entity[] jwLayer) {
        detachFromLayer();
        jwLayer[0].attachChild(effectContainer);
        jwLayer[0].attachChild(getTile());
        jwLayer[1].attachChild(tileAboveContainer);
        groundLayer = jwLayer[0];
        aboveLayer = jwLayer[1];
    }

    public void attachToEffectLayer(Entity layer) {
        detachFromLayer();
        layer.attachChild(effectContainer);
        layer.attachChild(getTile());
        layer.attachChild(tileAboveContainer);
        groundLayer = layer;
        aboveLayer = layer;
    }

    public void swapLayer(Entity[] sourceLayer, Entity[] targetLayer) {
        groundLayer.detachChild(getTile());
        groundLayer.detachChild(effectContainer);
        aboveLayer.detachChild(tileAboveContainer);
        targetLayer[0].attachChild(effectContainer);
        targetLayer[0].attachChild(getTile());
        targetLayer[1].attachChild(tileAboveContainer);
        groundLayer = targetLayer[0];
        aboveLayer = targetLayer[1];
    }

    public void detachFromLayer() {
        getTileGroundContainer().detachSelf();
        //detach enemy etc if present
        //FIXME getTileAboveContainer().detachChildren();
        getTileAboveContainer().detachSelf();
        effectContainer.detachSelf();
    }

    public void attachAndReveal(final int col, final int row, int index, final boolean onLevelStart) {
        matchActionPerformed = false;
        groundLayer = MainBoardScreen.getScm().getJwLayer()[col][row][0];
        aboveLayer = MainBoardScreen.getScm().getJwLayer()[col][row][1];
        groundLayer.attachChild(effectContainer);
        groundLayer.attachChild(this.tileGroundContainer);
        aboveLayer.attachChild(this.tileAboveContainer);
        //FIXME
        lsSprite.setAlpha(0f);
//        int delay = new Random().nextInt(10);
//        int delay = 10*(col+row)+(MathUtils.isEven(col+row)?col:row);
        float delay = 0;
        if (onLevelStart) {
            float maxDelay = (HeroAnimationController.getInstance().getHeroMovingToStartDuration());
            float minDelay = maxDelay / 100f;
            delay = new Random().nextInt(100) * minDelay;
        } else {
            delay = Math.abs(IConstants.totalTilesCount / 2 - index) * 0.02f;
        }
        final TileSprite currentTile = this;
        lsSprite.registerEntityModifier(new DelayModifier(delay, new IEntityModifier.IEntityModifierListener() {
            @Override
            public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
                //Log.i("loony", "TILE started attachAndReveal");
                if (onLevelStart) {
                    MainBoardScreen.getScm().getMainScene().registerUpdateHandler(new TimerHandler(1f, new ITimerCallback() {
                        @Override
                        public void onTimePassed(TimerHandler pTimerHandler) {
                            MainBoardScreen.getInstance().getGameSound().playMainLoopSound();
                        }
                    }));
                }
            }

            @Override
            public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
                lsSprite.registerEntityModifier(new AlphaModifier(onLevelStart ? 0.5f : 0.25f, 0f, 1f, new IEntityModifier.IEntityModifierListener() {
                    @Override
                    public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
                    }

                    @Override
                    public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
                        currentTile.setState(TileState.STATE_NORMAL);
                        TileBoard.tilesToReveal.remove(TileBoard.getKey(row, col));
                        if (TileBoard.tilesToReveal.size() == 0) {
                            TilesGameCycle.getInstance().setState(GameCycleState.CHECK);
                        }
                    }
                }));
            }
        }));
        float boostDelay;
        if (onLevelStart) {
            boostDelay = HeroAnimationController.getInstance().getHeroMovingToStartDuration();
            final Sprite crystal = TilesPool.getEntityForTexture(ResourceManager.getInstance().getMagicTileTextureRegion());
            crystal.setAlpha(0f);
            crystal.setAnchorCenter(0, 0);
            tileAboveContainer.attachChild(crystal);
            crystal.registerEntityModifier(
                    new DelayModifier(boostDelay, new IEntityModifier.IEntityModifierListener() {
                        @Override
                        public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
                        }

                        @Override
                        public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
                            /*new ParticleEmitter(5, 70, 0.5f, TileSpriteUtil.getTileCenterX(getTile().getX()), TileSpriteUtil.getTileCenterY(getTile().getY()),
                                    ResourceManager.getInstance().getGoldParticleRegion()).start();
                              */
                            MainBoardScreen.getInstanceEngine().runOnUpdateThread(new Runnable() {
                                @Override
                                public void run() {
                                    crystal.setAlpha(1);
                                    crystal.detachSelf();
                                    TilesPool.pushEntityToStack(crystal);
                                }
                            });
                        }
                    }));
        } else {
            // do not show particles on dead map, just recreate board
            boostDelay = Math.abs(IConstants.totalTilesCount / 2f) * 0.03f;
        }
    }

    public void moveWithDelay(float delay, final float[] newXY, final IEntityModifier.IEntityModifierListener listener) {
        final float[] oldXY = new float[]{this.getTile().getX(), this.getTile().getY()};
        setInMovement(true);
        this.getTile().registerEntityModifier(new DelayModifier(/*delay*/0f, new IEntityModifier.IEntityModifierListener() {
            @Override
            public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
            }

            @Override
            public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
                pItem.setAlpha(1);
                /*tileGroundContainer.setPosition(newXY[0], newXY[1]);
                tileGroundContainer.registerEntityModifier(new DelayModifier(0f, listener));
                tileAboveContainer.setPosition(newXY[0], newXY[1]);
                */
                float difX = Math.abs(newXY[0] - oldXY[0]) / 100f; //dif in tiles
                float jumpX = oldXY[0] + 0.99f * (newXY[0] - oldXY[0]);
                float jumpY = oldXY[1] + 0.99f * (newXY[1] - oldXY[1]);

                /*float ratio = (newXY[0] - oldXY[0]) / (newXY[1] - oldXY[1]);
                 jumpX = oldXY[0] + 3 * ratio * Math.signum(newXY[0] - oldXY[0]);
                 jumpY = oldXY[1] + 3 * Math.signum(newXY[1] - oldXY[1]);
                  */
                tileGroundContainer.registerEntityModifier(new SequenceEntityModifier(
                        new MoveSmoothModifier(0.5f + difX * 0.05f, oldXY[0], oldXY[1], newXY[0], newXY[1]),
                        new MoveModifier(0.01f, newXY[0], newXY[1], jumpX, jumpY),
                        new MoveModifier(0.01f, jumpX, jumpY, newXY[0], newXY[1], listener)

                )
                );
                tileAboveContainer.registerEntityModifier(new SequenceEntityModifier(
                        new MoveSmoothModifier(0.5f + difX * 0.05f, oldXY[0], oldXY[1], newXY[0], newXY[1]),
                        new MoveModifier(0.01f, newXY[0], newXY[1], jumpX, jumpY),
                        new MoveModifier(0.01f, jumpX, jumpY, newXY[0], newXY[1])
                )
                );
            }
        }));
    }

    public boolean isDragDropAllowed() {
        return true;
    }

    public final void performMatchAction(final int col, final int row) {
        //default match action = dissolve the tile

        /*new ParticleEmitter(15, 50, 1f,
                TileSpriteUtil.getTileCenterX(this.getTile().getX()),
                TileSpriteUtil.getTileCenterY(this.getTile().getY()),
                ResourceManager.getInstance().getParticleRegion()).start(); */
        performMatchAdditionalActions(col, row);
        if (temporaryMatchEffect == null) {
            performMatchEffect();
        } else {
            temporaryMatchEffect.run();
            temporaryMatchEffect = null;
        }
        performDissolveAction(col, row);
    }

    protected void performDissolveAction(int col, int row) {
        getDefaultDissolveAction(col, row).run();
    }

    protected Runnable getDefaultDissolveAction(final int col, final int row) {
        return new Runnable() {
            @Override
            public void run() {
                getLsSprite().registerEntityModifier(new SequenceEntityModifier(
                        new DelayModifier(temporaryMatchDissappearDelay),
                        new AlphaModifier(0.2f, 1, 0f,
                                new AfterMatchEntityModifierListener(TileBoard.getKey(row, col), TileSprite.this))));
                temporaryMatchDissappearDelay = 0;
            }
        };
    }

    protected Runnable getCrystalDissolveAction(final int col, final int row) {
        return new Runnable() {
            @Override
            public void run() {
                getTileGroundContainer().registerEntityModifier(new SequenceEntityModifier(
                        new DelayModifier(temporaryMatchDissappearDelay),
                        new ScaleAtModifier(0.2f, 1, 3f, 0.5f, 0.5f),
                        new ScaleAtModifier(0.1f, 3f, 1f, 0.5f, 0.5f)));
                getTileAboveContainer().registerEntityModifier(new SequenceEntityModifier(
                        new DelayModifier(temporaryMatchDissappearDelay),
                        new ScaleAtModifier(0.2f, 1, 3f, 0.5f, 0.5f),
                        new ScaleAtModifier(0.1f, 3f, 1f, 0.5f, 0.5f,
                                new AfterMatchEntityModifierListener(TileBoard.getKey(row, col), TileSprite.this))));
                temporaryMatchDissappearDelay = 0;
            }
        };
    }


    protected void performMatchAdditionalActions(final int col, final int row) {
        //do nothing by default
    }

    protected void performMatchEffect() {

        ParticleUtil.makeParticleCircleForTileMatch(
                TileSpriteUtil.getTileCenterX(this.getTile().getX()),
                TileSpriteUtil.getTileCenterY(this.getTile().getY())
        );
    }

    public int getCurrentCol() {
        return currentCol;
    }

    public void setCurrentCol(int currentCol) {
        this.currentCol = currentCol;
    }

    public int getCurrentRow() {
        return currentRow;
    }

    public void setCurrentRow(int currentRow) {
        this.currentRow = currentRow;
    }

    public void doDestroy(final Runnable afterDestroyActionFinished, final LinkedList<TileSprite> spritesInAction) {
        final TileSprite tile = this;
        if (this.lsSprite != null) {
            this.lsSprite.registerEntityModifier(
                    new SequenceEntityModifier(new DelayModifier(0.01f * new Random().nextInt(100)),
                            new ParallelEntityModifier(new MoveYModifier(0.3f, tile.lsSprite.getY(), tile.lsSprite.getY() - 100),
                                    new AlphaModifier(0.3f, 1.0f, 0f, new IEntityModifier.IEntityModifierListener() {
                                        @Override
                                        public void onModifierStarted(IModifier<IEntity> pModifier, IEntity pItem) {
                                            spritesInAction.add(tile);
                                        }

                                        @Override
                                        public void onModifierFinished(IModifier<IEntity> pModifier, IEntity pItem) {
                                            spritesInAction.remove(tile);
                                            MainBoardScreen.getInstanceEngine().runOnUpdateThread(new Runnable() {
                                                @Override
                                                public void run() {
                                                    tile.getTileGroundContainer().detachSelf();
                                                    tile.getTileAboveContainer().detachSelf();
                                                }
                                            });
                                            if (spritesInAction.size() == 0) {
                                                afterDestroyActionFinished.run();
                                            }
                                        }
                                    }))));
        } else {
            MainBoardScreen.getInstanceEngine().runOnUpdateThread(new Runnable() {
                @Override
                public void run() {
                    tile.getTileGroundContainer().detachSelf();
                    tile.getTileAboveContainer().detachSelf();
                }
            });
            if (spritesInAction.size() == 0) {
                afterDestroyActionFinished.run();
            }
        }

    }

    public AnimatedSprite getEnemyImp() {
        return enemyImp;
    }

    public void enableEnemyImp() {
        //final AnimatedSprite enemyImpOnTile = new AnimatedSprite(enemyImp.getX(), enemyImp.getY(), ResourceManager.getInstance().getEnemyImpRegion(), MainBoardScreen.getScm().getVertexBufferObjectManager());
        final AnimatedSprite enemyImpOnTile = new AnimatedSprite(20, 40, ResourceManager.getInstance().getEnemyImpRegion(), MainBoardScreen.getScm().getVertexBufferObjectManager());
        enemyImpOnTile.setAnchorCenter(0, 0);
        enemyImpOnTile.setAlpha(1f);
        enemyImpOnTile.animate(50);
        this.enemyImp = enemyImpOnTile;
        this.tileAboveContainer.attachChild(enemyImpOnTile);
    }


    public AnimatedSprite getFireOnTile() {
        return fireOnTile;
    }

    public void enableFireOnTile(float x, float y) {
        this.fireOnTile = new AnimatedSprite(40, 60, ResourceManager.getInstance().getFireOnTileRegion(), MainBoardScreen.getScm().getVertexBufferObjectManager());
        fireOnTile.animate(new long[]{50, 50, 50, 50, 50, 50, 50, 50, 50}, new int[]{8, 7, 6, 5, 4, 3, 2, 1, 0}, true);
        fireOnTile.setAnchorCenter(0, 0);
        fireOnTile.setAlpha(0);
        fireOnTile.setScale(1.5f);
        tileAboveContainer.attachChild(fireOnTile);
        fireOnTile.registerEntityModifier(new ParallelEntityModifier(
                new SequenceEntityModifier(
                        new DelayModifier(0.5f),
                        new AlphaModifier(0.2f, 0, 1),
                        new DelayModifier(0.3f)
                ),
                new SequenceEntityModifier(
                        new DelayModifier(0.5f),
                        new ScaleAtModifier(0.25f, 2f, 3f, 0.5f, 0.5f),
                        new ScaleAtModifier(0.25f, 3, 2, 0.5f, 0.5f)
                )
        ));
    }

    public AnimatedSprite.IAnimationListener getAnimationListener(int totalFrames) {
        return null;
    }

    public void setEnemyImp(AnimatedSprite enemyImp) {
        this.enemyImp = enemyImp;
    }

    public void setFireOnTile(AnimatedSprite fireOnTile) {
        this.fireOnTile = fireOnTile;
    }

    public Entity getTileGroundContainer() {
        return tileGroundContainer;
    }

    public Entity getTileAboveContainer() {
        return tileAboveContainer;
    }

    public Sprite getLsSprite() {
        return lsSprite;
    }

    public void setLsSprite(Sprite lsSprite) {
        this.lsSprite = lsSprite;
    }

    public boolean isMatchActionPerformed() {
        return matchActionPerformed;
    }

    public void setMatchActionPerformed(boolean matchActionPerformed) {
        this.matchActionPerformed = matchActionPerformed;
    }

    public void setEnemyImpInAction(boolean enemyImpInAction) {
        this.enemyImpInAction = enemyImpInAction;
    }

    public boolean isEnemyImpInAction() {
        return enemyImpInAction;
    }

    public void restartAnimation() {
    }

    public void setIsInDrag(boolean inDrag) {
        isInDrag = inDrag;
    }

    public boolean isInDrag() {
        return isInDrag;
    }

    public void setTemporaryMatchEffect(Runnable temporaryMatchEffect) {
        this.temporaryMatchEffect = temporaryMatchEffect;
    }

    public void setTemporaryMatchDissappearDelay(float temporaryMatchDissappearDelay) {
        this.temporaryMatchDissappearDelay = temporaryMatchDissappearDelay;
    }

    public boolean isInMovement() {
        return isInMovement;
    }

    public void setInMovement(boolean inMovement) {
        isInMovement = inMovement;
    }

    public void setUseDefaultDissolveAction(boolean useDefaultDissolveAction) {
        this.useDefaultDissolveAction = useDefaultDissolveAction;
    }

    public boolean isUseDefaultDissolveAction() {
        return useDefaultDissolveAction;
    }

    public Entity getEffectContainer() {
        return effectContainer;
    }
}
